package server

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"gitee.com/yuya000/cards-game-server-go/logwarp"
	"github.com/garyburd/redigo/redis"
	_ "github.com/go-sql-driver/mysql"
	"github.com/sirupsen/logrus"
	"github.com/xormplus/core"
	"github.com/xormplus/xorm"
	"io/ioutil"
	"math/rand"
	"net"
	"path"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var (
	ser       = newServer()
	RedisPool *redis.Pool
	DBEngine  *xorm.Engine
)

type Server struct {
	DebugMode       bool
	Listener        net.Listener
	CurrentConnMax  uint64
	LiveGames       sync.Map
	LivePlayer      sync.Map
	LivePlayerN     uint64
	LostPlayer      sync.Map
	LostPlayerGames sync.Map
	LiveGamesN      uint64
	Games           map[string]GameFactory
	mutex           sync.Mutex
}

func newServer() *Server {
	return &Server{
		Games: make(map[string]GameFactory),
	}
}

func SetupGame(name string, factory GameFactory) {
	ser.SetupGame(name, factory)
}

func (s *Server) SetupGame(name string, factory GameFactory) {
	s.Games[name] = factory
}

func (s *Server) Server() *Server {
	return s
}

func (s *Server) createClientCertPool(dir string) (*x509.CertPool, error) {
	fileInfos, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, err
	}
	clientCertPool := x509.NewCertPool()
	for _, fi := range fileInfos {
		if fi.IsDir() {
			continue
		}
		if strings.HasSuffix(fi.Name(), ".pem") {
			certbuf, err := ioutil.ReadFile(path.Join(Conf.Server.ClientCertDir, fi.Name()))
			if err != nil {
				return nil, err
			}
			if !clientCertPool.AppendCertsFromPEM(certbuf) {
				return nil, errors.New("添加客户端证书失败")
			}
		}
	}
	return clientCertPool, nil
}

func (s *Server) createTLSConfig() (*tls.Config, error) {
	conf := Conf.Server
	cert, err := tls.LoadX509KeyPair(conf.CertFilePath, conf.KeyFilePath)
	if err != nil {
		return nil, err
	}
	clientCertPool, err := s.createClientCertPool(conf.ClientCertDir)
	if err != nil {
		return nil, err
	}
	var clientAuthType = tls.NoClientCert
	switch conf.ClientAuthType {
	case "NoClientCert":
	case "RequestClientCert":
		clientAuthType = tls.RequestClientCert
	case "RequireAnyClientCert":
		clientAuthType = tls.RequireAnyClientCert
	case "VerifyClientCertIfGiven":
		clientAuthType = tls.VerifyClientCertIfGiven
	case "RequireAndVerifyClientCert":
		clientAuthType = tls.RequireAndVerifyClientCert
	default:
		return nil, fmt.Errorf("未知的配置文件选项值 %s", conf.ClientAuthType)
	}
	return &tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   clientAuthType,
		ClientCAs:    clientCertPool,
	}, nil
}

func (s *Server) createTCPListener() {
	logwarp.Std().Infoln("创建网络监听器")
	var (
		tlsConf *tls.Config
		err     error
	)
	if Conf.Server.EnableTLS {
		if tlsConf, err = s.createTLSConfig(); err != nil {
			logwarp.Std().Fatalln(err)
		}
	}
	if Conf.Server.EnableTLS {
		s.Listener, err = tls.Listen("tcp", Conf.Server.Listen, tlsConf)
	} else {
		s.Listener, err = net.Listen("tcp", Conf.Server.Listen)
	}
}

func (s *Server) initDBDrive() {
	logwarp.Std().Infoln("初始化MySQL数据库驱动")
	var err error
	DBEngine, err = xorm.NewMySQL(xorm.MYSQL_DRIVER, Conf.Db.DataSourceName)
	if err != nil {
		logwarp.Std().Errorln(err)
	}
	DBEngine.SetMaxOpenConns(Conf.Db.MaxOpenConns)
	DBEngine.SetMaxIdleConns(Conf.Db.MaxIdleConns)
	DBEngine.SetConnMaxLifetime(Conf.Db.ConnMaxLifetime.Duration)
	if s.DebugMode {
		//DBEngine.ShowSQL(true)
		DBEngine.SetLogLevel(core.LOG_DEBUG)
		DBEngine.SetLogger(&dbDebugLogger{})
	}
}

func (cs *Server) dialRedis() (redis.Conn, error) {
	conf := Conf.Redis
	var opts []redis.DialOption
	if conf.Network == "" || conf.Addr == "" {
		return nil, errors.New("redis初始化失败")
	}
	if d := conf.ConnTimeout.Duration; d > 0 {
		opts = append(opts, redis.DialConnectTimeout(d))
	}
	if conf.InitDb > 0 {
		opts = append(opts, redis.DialDatabase(conf.InitDb))
	}
	if conf.Password != "" {
		opts = append(opts, redis.DialPassword(conf.Password))
	}
	if d := conf.ReadTimeout.Duration; d > 0 {
		opts = append(opts, redis.DialReadTimeout(d))
	}
	if d := conf.WriteTimeout.Duration; d > 0 {
		opts = append(opts, redis.DialWriteTimeout(d))
	}
	return redis.Dial(conf.Network, conf.Addr, opts...)
}

func (cs *Server) redisConnTest(c redis.Conn, t time.Time) error {
	_, err := redis.String(c.Do("PING"))
	return err
}

func (cs *Server) initRedisPool() {
	logrus.Infoln("初始化Redis池")
	RedisPool = &redis.Pool{
		Dial:         cs.dialRedis,
		TestOnBorrow: cs.redisConnTest,
		MaxIdle:      Conf.Redis.PoolMaxIdle,
		MaxActive:    Conf.Redis.MaxActive,
		Wait:         Conf.Redis.Wait,
		IdleTimeout:  Conf.Redis.IdleTimeout.Duration,
	}
}

func (s *Server) onConnEnding(session *Session) {
	logwarp.Std().Debugln("关闭连接")
	session.Close()
	atomic.AddUint64(&s.CurrentConnMax, ^uint64(0))
}

func (s *Server) panicTayCatch(session *Session, mt uint16) {
	if r := recover(); r != nil {
		logwarp.Std().Errorln(r)
	}
}

func (s *Server) createGame(player *Player, message *Message) (Game, Exception) {
	logwarp.Std().Infoln("创建游戏")
	if _, exist := s.LostPlayer.Load(player.Id); exist {
		logwarp.Std().Warningln("掉线玩家 不能创建游戏")
		return nil, InvalidOptException
	}
	var newGameReq NewGameReq
	if err := MsgBodyParser(message.Data, &newGameReq); err != nil {
		logwarp.Std().Warningln(err)
		return nil, InvalidOptException
	}
	args := (Args)(newGameReq.Args)
	logwarp.Std().Debugln("创建游戏参数", newGameReq)
	factory, exist := s.Games[newGameReq.Type]
	if !exist {
		logwarp.Std().Debugln("非法操作", newGameReq.Type)
		return nil, InvalidOptException
	}
	game, ex := factory.NewGame(player, args)
	if ex != nil {
		logwarp.Std().Warningln(ex)
		return nil, ex
	}
	if err := LiveMark(player.Id, game.Id(), game.Name()); err != nil {
		logwarp.Std().Warningln("PlayerLiveMark", err)
		factory.Free(game)
		return nil, SystemException
	}
	s.addPlayer(player)
	s.addGame(game)
	return game, ex
}

func (s *Server) joinGame(player *Player, message *Message) (Game, Exception) {
	logwarp.Std().Infoln("加入游戏")
	if _, exist := s.LostPlayer.Load(player.Id); exist {
		logwarp.Std().Warningln("掉线玩家 不能加入游戏")
		return nil, InvalidOptException
	}
	var joinGameReq JoinGameReq
	if err := MsgBodyParser(message.Data, &joinGameReq); err != nil {
		logwarp.Std().Warningln(err)
		return nil, InvalidOptException
	}
	g, exist := s.LiveGames.Load(joinGameReq.HouseNo)
	if !exist {
		logwarp.Std().Debugln(joinGameReq.HouseNo)
		return nil, GameNotFoundException
	}
	game := g.(Game)
	if game.GetOwner().CurrentClub.ClubId != player.CurrentClub.ClubId {
		logwarp.Std().Warningln("不同俱乐部之间不能玩耍")
		return nil, GameNotFoundException
	}
	if err := LiveMark(player.Id, game.Id(), game.Name()); err != nil {
		logwarp.Std().Warningln(err)
		return nil, SystemException
	}
	ex := game.JoinGame(player)
	if ex != nil {
		LiveUnmark(player.Id)
		return nil, ex
	}
	s.addPlayer(player)
	logwarp.Std().Infoln(game.Id())
	logwarp.Std().Infoln(game.Name())
	return game, ex
}

func (s *Server) onConn(session *Session) {
	atomic.AddUint64(&s.CurrentConnMax, 1)

	defer s.onConnEnding(session)

	logwarp.Std().Debugln("onConn 读取消息", session.Id)
	message, err := ReadMessage(session)
	if err != nil {
		logwarp.Std().Warningln(err)
		return
	}
	logwarp.Std().Debugln(message)

	defer s.panicTayCatch(session, message.Header.MessageType)

	var ex Exception
	player, ex := NewPlayer(session, message.Req.Token)
	if ex != nil {
		logwarp.Std().Warningln(ex)
		SendException(session, ex, message.Header.MessageType)
		return
	}

	logwarp.Std().Debugln("创造玩家", player)

	if _, exist := s.LivePlayer.Load(player.Id); exist {
		logwarp.Std().Warningln("已经在线，不能重复创建或加入游戏")
		SendException(session, InvalidOptException, message.Header.MessageType)
		return
	}
	var (
		game Game
	)
	switch message.Header.MessageType {
	case MessageTypeCreateGame:
		if game, ex = s.createGame(player, message); ex != nil {
			SendException(session, ex, message.Header.MessageType)
			return
		}
	case MessageTypeJoinGame:
		if game, ex = s.joinGame(player, message); ex != nil {
			SendException(session, ex, message.Header.MessageType)
			return
		}
	case MessageTypeResumeGame:
		logwarp.Std().Infoln("玩家重新加入游戏")
		if game, ex = s.resumeGame(player); ex != nil {
			SendException(session, ex, message.Header.MessageType)
			return
		}
	default:
		logwarp.Std().Debugln("未知的消息类型", message.Header.MessageType)
		SendException(session, InvalidOptException, message.Header.MessageType)
		return
	}
	session.Flush()
	s.playGame(player, game)
}

func (s *Server) playerLost(player *Player, game Game) {
	player.Stat = PlayerLost
	s.delPlayer(player)
	s.LostPlayer.Store(player.Id, game)
	if err := game.Broadcast(player, MessageTypeLostConn, &LostConnResp{Id: player.Id}); err != nil {
		logwarp.Std().Warningln(err)
	}
}

func (s *Server) playerQuit(player *Player, game Game) {
	LiveUnmark(player.Id)
	s.LostPlayer.Delete(player.Id)
	s.delPlayer(player)
}

func (s *Server) pong(player *Player) {
	if err := player.Unicast(&Pong{Content: "pong"}, MessageTypePong); err != nil {
		logwarp.Std().Warningln(err)
	}
}

func (s *Server) talkDispatcher(message *Message, sender *Player, game Game) {
	var talk PlayerTalkReq
	if err := MsgBodyParser(message.Data, &talk); err != nil {
		logwarp.Std().Warningln(err)
		SendException(sender.Session, InvalidOptException, MessageTypeTalk)
	} else {
		logwarp.Std().Debugln("talk", sender.Id, talk.Content)
		if err := game.Broadcast(sender, MessageTypeTalk, &PlayerTalkNotice{
			Sender:  sender.Id,
			Content: talk.Content,
		}); err != nil {
			logwarp.Std().Warningln(err)
		}
	}
}

func (s *Server) playerQuitHook(player *Player, startTime time.Time) {
	now := time.Now()
	if t := now.Sub(startTime) / time.Duration(time.Minute*10); t > 0 { // 每十分钟奖励1分
		logwarp.Std().Infoln("系统奖励积分", t)
		logwarp.Std().Debugln(DBEngine.ID(player.Id).Incr("credit", t).Update(&User{}))
	}
}

func (s *Server) playGameExitHook(player *Player, game Game) {
	if game.GameOver() {
		logwarp.Std().Debugln("游戏正常退出")
		for _, player := range game.Players() {
			s.playerQuit(player, game)
		}
		s.delGame(game)
	} else {
		switch player.Stat {
		case PlayerQuit:
			logwarp.Std().Debugln("玩家离开游戏", player)
			s.playerQuit(player, game)
		default:
			logwarp.Std().Warningln("玩家掉线", player)
			s.playerLost(player, game)
		}
	}
	s.playerQuitHook(player, time.Now())
}

func (s *Server) playGame(player *Player, game Game) {
	defer s.playGameExitHook(player, game)
	for {
		//logwarp.Std().Debugln("等待读取消息", "ReadMessage")
		message, err := ReadMessage(player.Session)
		if err != nil {
			logwarp.Std().Warningln(err)
			return
		}
		//logwarp.Std().Debugln(message)
		switch message.Header.MessageType {
		case MessageTypePing:
			s.pong(player)
		case MessageTypeTalk:
			s.talkDispatcher(message, player, game)
		default:
			game.Play(player, message)
		}
		player.Session.Flush()
	}
}

func (s *Server) resumeGame(player *Player) (Game, Exception) {
	g, exist := s.LostPlayer.Load(player.Id)
	if !exist {
		logwarp.Std().Warningln("非法操作,用户并没有掉线")
		return nil, InvalidOptException
	}
	game := g.(Game)
	if ex := game.ResumeGame(player); ex != nil {
		return nil, ex
	}
	s.LostPlayer.Delete(player.Id)
	s.addPlayer(player)
	player.Stat = PlayerStatNomal
	return game, nil
}

func (s *Server) addGame(game Game) {
	s.LiveGames.Store(game.Id(), game)
	atomic.AddUint64(&s.LiveGamesN, 1)
}

func (s *Server) delGame(game Game) {
	s.LiveGames.Delete(game.Id())
	atomic.AddUint64(&s.LiveGamesN, ^uint64(0))
}

func (s *Server) addPlayer(player *Player) {
	s.LivePlayer.Store(player.Id, player)
	atomic.AddUint64(&s.LivePlayerN, 1)
}

func (s *Server) delPlayer(player *Player) {
	s.LivePlayer.Delete(player.Id)
	atomic.AddUint64(&s.LivePlayerN, ^uint64(0))
}

func (s *Server) maxConnsCheck() error {
	if Conf.Server.MaxConns > 0 {
		if atomic.LoadUint64(&s.CurrentConnMax) >= uint64(Conf.Server.MaxConns) {
			return fmt.Errorf("已经达到最大连接上线 %d", Conf.Server.MaxConns)
		}
	}
	return nil
}

func (s *Server) looper() error {
	for {
		conn, err := s.Listener.Accept()
		if err != nil {
			logwarp.Std().Warningln(err)
			return err
		}
		logwarp.Std().Infoln("remote -> ", conn.RemoteAddr())
		func() {
			s.mutex.Lock()
			defer s.mutex.Unlock()
			if err := s.maxConnsCheck(); err != nil {
				logwarp.Std().Warningln(err)
				conn.Close()
				return
			}
			session := NewSession(conn)
			go s.onConn(session)
		}()
	}
	return nil
}

func (s *Server) checkRunMode() {
	if strings.ToLower(Conf.RunMode) == "debug" {
		s.DebugMode = true
	}
}

func (s *Server) initLogger() {
	if s.DebugMode {
		logrus.SetLevel(logrus.DebugLevel)
	} else {
		switch Conf.LoggerLevel {
		case "none":
			logrus.SetLevel(logrus.PanicLevel)
		case "warn":
			logrus.SetLevel(logrus.WarnLevel)
		case "info":
			logrus.SetLevel(logrus.InfoLevel)
		case "error":
			logrus.SetLevel(logrus.ErrorLevel)
		default:
			logrus.SetLevel(logrus.ErrorLevel)
		}
	}
	switch Conf.LoggerFormat {
	case "text":
		logrus.SetFormatter(&logrus.TextFormatter{})
	default:
		logrus.SetFormatter(&logrus.JSONFormatter{})
	}
}

func Start() error {
	if !Conf.loaded {
		return errors.New("没有加载配置文件")
	}
	rand.Seed(time.Now().UnixNano())
	ser.checkRunMode()
	ser.initLogger()
	ser.createTCPListener()
	ser.initDBDrive()
	ser.initRedisPool()
	return ser.looper()
}

type dbDebugLogger struct{}

func (logger *dbDebugLogger) Debug(v ...interface{}) { logwarp.Std().Debugln(v...) }

func (logger *dbDebugLogger) Debugf(format string, v ...interface{}) {
	logwarp.Std().Debugf(format, v...)
}

func (logger *dbDebugLogger) Error(v ...interface{}) { logwarp.Std().Warningln(v...) }

func (logger *dbDebugLogger) Errorf(format string, v ...interface{}) {
	logwarp.Std().Warningf(format, v...)
}

func (logger *dbDebugLogger) Info(v ...interface{}) {
	logwarp.Std().Infoln(v...)
}

func (logger *dbDebugLogger) Infof(format string, v ...interface{}) {
	logwarp.Std().Infof(format, v...)
}

func (logger *dbDebugLogger) Warn(v ...interface{}) {
	logwarp.Std().Warning(v...)
}

func (logger *dbDebugLogger) Warnf(format string, v ...interface{}) {
	logwarp.Std().Warnf(format, v...)
}

func (logger *dbDebugLogger) Level() core.LogLevel {
	return core.LOG_DEBUG
}

func (logger *dbDebugLogger) SetLevel(l core.LogLevel) {}

func (logger *dbDebugLogger) ShowSQL(show ...bool) {}

func (logger *dbDebugLogger) IsShowSQL() bool { return true }

const (
	PlayerLiveMarkPrefix = "player_live_"
)

func GetPlayerLiveMarkKey(id string) string {
	return fmt.Sprintf("%s%s", PlayerLiveMarkPrefix, id)
}

func LiveMark(uid string, gameId string, gameName string) error {
	redisConn := RedisPool.Get()
	_, err := redis.String(redisConn.Do("HMSET", GetPlayerLiveMarkKey(uid), "game_name", gameName, "game_id", gameId))
	if err != nil {
		return err
	}
	return nil
}

func LiveUnmark(uid string) error {
	redisConn := RedisPool.Get()
	_, err := redis.Int(redisConn.Do("DEL", GetPlayerLiveMarkKey(uid)))
	if err != nil {
		return err
	}
	return nil
}
